program DAShell;

{	;+++																											}
{	;				Copyright  1989 Symantec Corporation. All rights reserved.						}
{	;				Symantec Corporation, Language Products Group.										}
{	;																												}
{	;	This document is disclosed as an aid for programming in the THINK Pascal 					}
{	;	environment.																								}
{	;																												}
{	;	The information in this software is subject to change without notice and should not be		}
{	;	construed as a commitment by Symantec.															}
{	;																												}
{	;	Symantec assumes no responsibility for the use or reliability of its software				}
{	;	on equipment which is not supported.																}
{	;																												}
{	;---																											}

{	;+++																											}
{	;	Author		:	Terry L. Lucas																		}
{	;	Version		:	2.0																					}
{	;																												}
{	;	Abstract	:																								}
{	;	--------																									}
{	;		DA Shell is a program designed to to aid THINK Pascal users in building and de -			}
{	;		bugging desk accessories.  This program works on the premise that all DAs written		}
{	;		in THINK Pascal have the following calling sequence :											}
{	;																												}
{	;			FUNCTION Main (theDCE : DCtlPtr;  IOPB : ParmBlkPtr;  sel : Integer) : OSErr;		}
{	;																												}
{	;		and that all DAs must be self contained in a Unit.												}
{	;																												}
{	;		Simply adding the unit name in the "USES" clause of DAShell and adding that unit to 	}
{	;		the DAShell Project will allow your DA to run within the Pascal environment.			}
{	;																												}
{	;		DAShell simulates how the Macintosh controls a DA.  DAShell builds a fake device		}
{	;		control entries (DCE) and a fake I/O parameter block (IOPB) by handling all events		}
{	;		and funneling them to the DA.  																	}
{	;																												}
{	;		The same DA code that works as a normal DA should not have to be modified to work	}
{	;		in the DAShell environment.  There are some considerations,  as follows:					}
{	;																												}
{	;					1.  AccEvent "inSysWindow" for normal DA should case on "inContent" and	}
{	;						"inSysWindow".  For DAShell the DA windows look like "inContent" 		}
{	;						and DA stand-a-lone look like "inSysWindow".									}
{	;																												}
{	;					2.	Owned DA resoures should be determined by the dCtlRefNum in the DCE	}
{	;						and the actual dCtlRefNum should be calculated as the absolute				}
{	;						dCtlRefNum minus 1.  The reason for this is that the dCtlRefNum in the	}
{	;						DAShell is always (RefNum + 1) and in a real DA the RefNum is				}
{	;						-(RefNum + 1).																		}
{	;																												}
{	;					3.	The GoodBye kiss in a real DA is done asynchronously, this implies 		}
{	;						that the DA must clean-up and then JMP to JIODone to remove the			}
{	;						I/O request, mark DA inactive and unlocked and execute the completion	}
{	;						routine.  In DAShell the goodBye kiss is done synchronously.				}
{	;																												}
{	;					4.	Normally on a _Close to a driver the driver if RAM based will mark 		}
{	;						the driver code as purgeable.  To support this correctly in the DAShell	}
{	;						you the DA writer will have to test the dCtlRefNum of the DA and if 		}
{	;						negative do a HPurge otherwise don't.											}
{	;																												}
{	;	Using DAShell																							}
{	;	-------------																							}
{	;		DA Shell understands most tasks which a DA can do.  For example dNeedGoodBye		}
{	;		is posted to the SampleDA by quitting from the DAShell "Quit" menu item and having	}
{	;		the SampleDA opened.  The handling of dNeedTime will is also done if drvrDelay and		}
{	;		dNeedTime is set in the drvrFlags and the time from the last invocation + drvrDelay	}
{	;		is greater than current tickCount.																}
{	;																												}
{	;										Modification History												}
{	;																												}
{	;	   Date			Initials									Comments									}
{	;	   ----			------		--------------------------------------------------------	}
{	;	08/09/86	  	TLL		Creation of DAShell.													}
{	;	08/10/86	  	TLL		Adjusted CheckMenu to handle multiple/single menus in a DA.	}
{	;									Don't allow "FAN" keys in Edit menu to be passed as "AccXXX".	}
{	;	08/11/86		TLL		Added code to check for Control calls enabled in dCtlFlags be-	}
{	;									fore calling the SampleDA.  Added code to make all driver calls	}
{	;									to the DA IMMED (synchronous).										}
{	;	05/02/88		JMM		dCtlStorage := NewHandle(0) for A4-world compatibility.		}
{	;	10/09/88		RMS		in OpenSampleDA, show DCE->dCtlWindow if not NIL				}
{	;	10/10/88		RMS		Fixed bug which caused null events to not be passed to the DA	}
{	;									as accCursor calls.														}
{	;						RMS		Replaced instances of "GOTO" with "CYCLE" for readability.		}
{	;						RMS		Updated legalese at beginning of program to reflect new			}
{	;									product and company names; also changed version to 2.0		}
{	;	09/21/89		pb			Fixed bug where real DAs got SystemClick twice.					}
{	;									Changed header once more. 											}
{	;	11/26/89		brp			RunDA:added code to make DA look like it's closed.  This more	}
{	;									closely approximates the THINK glue								}
{	; 	11/26/89		brp			changed OpenSampleDA and RunDA to make sure DA gets one		}
{	;									Open call instead of two												}
{	;	11/26/89		brp			changed BitAnd to BAND, and BitTst to BTST (updating			}
{	;									parameters, of course)												}
{	;	11/26/89		brp			HandleMBar:assign to csparam directly instead of blockmove	}
{	;	11/27/89		brp			added inDrag case to main event loop								}
{	;----------------------------------------------------------------------------------	}
{	;---																											}

   {Modify this uses clause.}

	uses
		Minesweeper;												{This name should be your DA Unit}

	const
		DARefNum = 12;											{Default RefNum of the DA}

	{Selector values for calls to a driver.}
		TheOpen = 0;
		ThePrime = 1;
		TheControl = 2;
		TheStatus = 3;
		TheClose = 4;

	{DA action requests}
		accEvent = 64;
		accRun = 65;
		accCursor = 66;
		accMenu = 67;
		accUndo = 68;
		accCut = 70;
		accCopy = 71;
		accPaste = 72;
		accClear = 73;

	{csCode value when the Application heap is about to be initialized.}
		goodBye = -1;

	{Bit settings high byte of dCtlFlag}
		dReadEnable = 8;											{Driver responds to _Read}
		dWriteEnable = 9;										{Driver responds to _Write}
		dCtlEnable = 10;											{Driver responds to _Control}
		dStatEnable = 11;										{Driver responds to _Status}
		dNeedGoodBye = 12;										{Driver notified prior to InitApplZone}
		dNeedTime = 13;											{Driver needs periodic time}
		dNeedLock = 14;											{Driver will be locked as soon as openned}

	var
		fakeDCE: DCtlEntry;										{Fake DCE}
		fakeIOPB: ParamBlockRec;								{Fake IOPB}
		theEvent: EventRecord;									{Current event}
		actionControl: Integer;									{Current DA action}
		driverCall: Integer;										{Current DA driver call}
		oldMenu: Integer;										{The actual negative menuID for Sample DA}

		done: Boolean;											{Life of DAShell}
		hitWhere: Integer;										{Where did the event occur}
		aWindow: WindowPtr;									{Which window did it occur in}
		applMenus: array[1..3] of MenuHandle;			{DAShell menus}
		error: OSErr;

		evnt: Boolean;											{Unused variable for return val of GetNextEvent}

	procedure DisplayMenuBar;
		var
			i: Integer;
	begin
		applMenus[1] := NewMenu(1, Chr(appleMark));
		AppendMenu(applMenus[1], 'Sample DA');				{This is the user's Sample DA}
		AppendMenu(applMenus[1], '(-');
		InsertResMenu(applMenus[1], 'DRVR', 2);				{Real DAs follow}

		applMenus[2] := NewMenu(2, 'File');
		AppendMenu(applMenus[2], 'Close');					{Closes any DA}
		AppendMenu(applMenus[2], 'Quit/Q');					{Forces a goodByeKiss}

		applMenus[3] := NewMenu(3, 'Edit');					{Used to force edit action items}
		AppendMenu(applMenus[3], 'Undo/Z');
		AppendMenu(applMenus[3], '(-');
		AppendMenu(applMenus[3], 'Cut/X');
		AppendMenu(applMenus[3], 'Copy/C');
		AppendMenu(applMenus[3], 'Paste/V');
		AppendMenu(applMenus[3], 'Clear');

		for i := 1 to 3 do
			InsertMenu(applMenus[i], 0);
		DrawMenuBar;											{Show the sucker}
	end;		{End of DisplayMenuBar PROCEDURE}

	procedure RunDA;
	begin
		if fakeDCE.dCtlDriver <> nil then						{Sample DA loaded?}
			begin
		{Yes, is the call a Control and are control calls accepted?}
		{Set all of our traps to be IMMED (synchronous) instead of ASYNCHronous.  Even the goodBye}
		{kiss is done synchronously in DAShell, remember in a real DA the goodBye kiss is done w/o}
		{the IMMED bit set this implies that the user must do a jmp to JIODone.  All real asynchronous}
		{request must be completed with JIODone to remove the  the I/O Request from the queue and}
		{it marks the DA inactive and unlocked and executes the completion routine if there is one.}
				case driverCall of
					TheOpen: 
						fakeIOPB.ioTrap := $A200;						{_Open with IMMED bit set.}
					TheControl: 
						fakeIOPB.ioTrap := $A204;						{_Control with IMMED bit set.}
					TheClose: 
						fakeIOPB.ioTrap := $A201;						{_Close with IMMED bit set.}
					otherwise
						sysbeep(0);
				end;

				if (driverCall <> TheControl) or BTST(fakeDCE.dCtlFlags, dCtlEnable) then
					begin
						error := Main(@fakeDCE, @fakeIOPB, driverCall);	{Yes, so call it.}

						if (drivercall = TheOpen) and (error = noerr) then
							begin
				{If after the _Open needTime was set then set up when the next time the DA should run.}
								if BTST(fakeDCE.dCtlFlags, dNeedTime) then
									fakeDCE.dCtlCurTicks := TickCount + fakeDCE.dCtlDelay;

				{If the DA has a window, then show it.}
								if fakeDCE.dCtlWindow <> nil then
									ShowWindow(WindowPtr(fakeDCE.dCtlWindow));
							end;

			{If we just told the da to close, then check the return value.  If it's <0, then the DA didn't close}
			{If it's 0, the DA closed OK.  If it's 1, this is a special case where the DA closed, but wants its globals}
			{in dCtlStorage preserved.  This case is handled by the THINK Pascal header; the system sees a return of 0}
						if (DriverCall = TheClose) then
							begin
								if error = noerr then
									if fakeDCE.dctlStorage <> nil then
										begin
											disposHandle(fakeDCE.dctlStorage);
											fakeDCE.dctlStorage := nil;
										end;

								if (error = noerr) or (error = 1) then
									fakedce.dctldriver := nil;
							end;
					end;
			end;
	end;		{End of RunDA PROCEDURE}

	procedure OpenSampleDA;
	begin
		if fakeDCE.dCtlDriver = nil then						{DA already openned?}
			begin													{No so initialize the DCE and IOPB}
				with fakeDCE do										{Create our fake DCE}
					begin
						dCtlDriver := @Main;
						dCtlFlags := $0400;								{This is the flag that THINK Pascal makes for a DA}
						dCtlPosition := 0;
						dCtlStorage := NewHandle(0);					{So DA will think it has a real A4 world}
						dCtlRefNum := DARefNum + 1;
						dCtlCurTicks := TickCount;
						dCtlWindow := nil;
						dCtlDelay := 0;
						dCtlEMask := $016A;							{This is the event mask THINK Pascal makes for a DA}
						dCtlMenu := 0;
					end;
				with fakeIOPB do									{Create our fake IOPB}
					begin
						qType := Ord(ioQType);							{Our drive type is this.}
						ioCRefNum := DARefNum + 1;					{This is the refNum}
						csCode := 0;
					end;
			end;

		driverCall := TheOpen;									{Open the SampleDA}
	end;		{End of OpenSampleDA PROCEDURE}

	procedure HandleMBar;
		const
			appleMenu = 1;
			fileMenu = 2;
			editMenu = 3;
		var
			menuHit: record
					case boolean of
						True: (
								longWord: LongInt
						);
						False: (
								theMenuID: Integer;
								theMenuItem: Integer;
						);
				end;
			theName: Str255;
			junk: Integer;
			aWindow: WindowPeek;

	begin
		if theEvent.what = mouseDown then
			menuHit.longWord := MenuSelect(theEvent.where)
		else
			menuHit.longWord := MenuKey(chr(theEvent.message mod 256));

		case menuHit.theMenuID of
			0: 
				;												 		{Do nothing!  We didn't select anything.}

			appleMenu: 
				if menuHit.theMenuItem = 1 then					{Open our SampleDA?}
					OpenSampleDA										{Yes, so lets do it.}
				else
					begin												{No, open a normal DA}
						GetItem(applMenus[1], menuHit.theMenuItem, theName);
						junk := OpenDeskAcc(theName);
					end;

			fileMenu: 
				if menuHit.theMenuItem = 1 then					{Close?}
					begin												{Yes, should we close a real or fake DA?}
						aWindow := WindowPeek(FrontWindow);
						if aWindow <> nil then
							if aWindow^.windowKind < 0 then			{Is it a real DA?}
								CloseDeskAcc(aWindow^.windowKind)		{Yes, so do a normal close of a real DA.}
							else
								driverCall := TheClose;						{No, close our Sample DA.}
					end
				else if menuHit.theMenuItem = 2 then			{Did we get a QUIT?}
					begin
						driverCall := TheClose;							{Yes, close the DA and mark for finishing.}
						done := True;
					end;

			editMenu: 												{Handle real DA's}
				if not (SystemEdit(menuHit.theMenuItem - 1)) or (fakeDCE.dCtlDriver <> nil) then
		  {If a non-mouseDown event caused the menu do be selected then we want to simulate what	}
		  {the Desk Manager does and not pass an AccXXX code but let the DA decipher the event.	}
					if theEvent.what = mouseDown then
						case menuHit.theMenuItem of
							1: 
								fakeIOPB.csCode := accUndo;
							3: 
								fakeIOPB.csCode := accCut;
							4: 
								fakeIOPB.csCode := accCopy;
							5: 
								fakeIOPB.csCode := accPaste;
							6: 
								fakeIOPB.csCode := accClear;
						end;		{End of CASE statement}

			otherwise
			{See if our SampleDA menu was hit.}
				if (fakeDCE.dCtlDriver <> nil) then
					begin
						fakeIOPB.csCode := accMenu;
						fakeIOPB.csParam[0] := menuhit.theMenuID;
						fakeIOPB.csParam[1] := menuhit.theMenuItem;
					end
		end;	{End of CASE statement}
		HiliteMenu(0);												{Unhilite menu, real or fake DA menu hit.}
	end;		{End of HandleMBar PROCEDURE}

	procedure SomeTime;
	begin
	{Make sure the DA is running and has needTime set because we're using values in the DCE}
		if (fakeDCE.dCtlDriver <> nil) then
			if BTST(fakeDCE.dCtlFlags, dNeedTime) then
				if TickCount > fakeDCE.dCtlCurTicks then				{Has the time passed yet?}
					begin														{Yes, so call DA again.}
						fakeIOPB.csCode := accRun;							{Give a slice of time.}
						driverCall := TheControl;
						RunDA;
		   {Calculate the next interval to run the DA.}
						fakeDCE.dCtlCurTicks := TickCount + fakeDCE.dCtlDelay;
					end;
	end;		{End of SomeTime PROCEDURE}

	procedure DoDACursor;
	begin
{Also, give accCursor calls to the DA on a regular basis}
		if (fakeDCE.dCtlDriver <> nil) and BTST(fakeDCE.dCtlFlags, dNeedTime) then
			begin
				fakeIOPB.csCode := accCursor;
				driverCall := TheControl;
				RunDA;
			end;
	end;

	procedure CheckMenu;
		var
			mBarEnabled: ^Integer;									{Pointer to the MBarEnabled global}
			mHandle: MenuHandle;										{Current DA Menu handle}
	begin
	{+++																											}
	{Check if the SampleDA has installed a menu which is always negative for DAs.  If one is installed	}
	{then we'll set the global to say there isn't a DA running and we also have to change the MenuID to	}
	{positive so we update the MenuHandle.																	}
	{---																											}
		mBarEnabled := Pointer($A20);								{Where the MBarEnabled global is.}

	{If the SampleDA has a multiple menus lets fix it up.}
		if fakeDCE.dCtlMenu < 0 then
			begin
				oldMenu := fakeDCE.dCtlMenu;							{Remember this negative value.}
				mHandle := GetMHandle(fakeDCE.dCtlMenu);
				DeleteMenu(fakeDCE.dCtlMenu);							{Remove negative DA menuID.}
				fakeDCE.dCtlMenu := -fakeDCE.dCtlMenu;				{Update the actual menuID}
				if mHandle <> nil then
					begin
						mHandle^^.menuID := fakeDCE.dCtlMenu;			{Adjust the DCE menuID}
						InsertMenu(mHandle, 0);							{Add the DA menu as a positive menuID.}
						DrawMenuBar;
					end;
			end;
		if mBarEnabled^ = oldMenu then							{Force mBarEnabled to zero if it becomes}
			mBarEnabled^ := 0;											{negative.}
	end;		{End of CheckMenu PROCEDURE}

begin																{Beginning of DAShell}
	oldMenu := 0;
	InitCursor;
	fakeDCE.dCtlDriver := nil;									{Mark the Sample DA as not loaded}
	DisplayMenuBar;

	done := False;
	repeat
		CheckMenu;
		SystemTask;													{In case we have a real DA}
		SomeTime;													{Here's some spare time for the DA.}

		evnt := GetNextEvent(everyEvent, theEvent);			{Fetch the next available event}

		fakeIOPB.csCode := accEvent;							{We got an event}
		driverCall := TheControl;
		fakeIOPB.ioMisc := @theEvent;							{Set up the pointer to the event record.}

		case theEvent.what of
			nullEvent: 
				fakeIOPB.csCode := accCursor;

			mouseDown: 
				begin
					hitWhere := FindWindow(theEvent.where, aWindow);
					case hitWhere of
						inMenuBar: 
							HandleMBar;

						inSysWindow: 									{Real DA so handle it.}
							begin														{ pb 9/21/89 START }
								SystemClick(theEvent, aWindow);
								Cycle
							end;															{ pb 9/21/89 END }

						inContent:										{Is the event for the DA?}
							if BAND(fakeDCE.dCtlEMask, mDownMask) = 0 then
								Cycle			{no, so skip it.}
							else if FrontWindow <> WindowPtr(aWindow) then {If the window we've clicked is not the front window then activate the window.}
								begin
									SelectWindow(aWindow);					{Post an activate event.}
									Cycle;						{but not a MouseDown}
								end;

						inDrag: 
							DragWindow(aWindow, theEvent.where, screenbits.bounds);

						inGoAway:
				  {If we hit the goAway box close the SampleDA}
							if TrackGoAway(aWindow, theEvent.where) then
								driverCall := TheClose;

						otherwise

					end;		{End of hitWhere CASE statement}
				end;		{End of mouseDown code}

			keyDown, autoKey: 
				if BAND(theEvent.modifiers, cmdKey) <> 0 then	{Fan Key depressed check if menu}
					HandleMBar;

			otherwise
			{Check the dCtlEMask to see if the SampleDA needs this event.}
				if not BTST(fakeDCE.dCtlEMask, theEvent.what) then
					begin
						fakeIOPB.csCode := 0;	{it doesn't so just cycle through the loop again.}
						Cycle;
					end;
		end;		{End of theEvent.what CASE statement}

  	{Do a Good Bye Kiss...if we're quitting.}
		if BTST(fakeDCE.dCtlFlags, dNeedGoodBye) and done then
			fakeIOPB.csCode := goodBye;			{SMACK!!!, was it as good for you as for me?}

		RunDA;									{Call the DA with the fake DCE and param block.}

		if driverCall = TheClose then		{Mark DA as closed, and reset the cursor.}
			begin
				fakeDCE.dCtlDriver := nil;
				InitCursor;
			end;
	until done;

end.	{End of DAShell}